#include "Whole.h"
#include <regex>

using namespace Science::Mathematics::Numbers;

Whole::Whole()
{
	_number.clear();
}

Whole::Whole(const Whole& pSrc)
{
	_number = pSrc._number;
}

Whole::Whole(Whole&& pSrc)
{
	_number = pSrc._number;
	pSrc._number.clear();
}

Whole::Whole(unsigned __int64 pNum)	
{
	parseNum(pNum);
}

Whole::Whole(std::string numStr)
{
	parseNum(numStr);
}

Whole::~Whole()
{ 
	_number.clear();
}

Whole::operator unsigned __int64()
{
	return getValue();
}

Whole& Whole::operator=(const Whole& pSrc)
{
	if (this == &pSrc)
		return *this;

	_number = pSrc._number;

	return *this;
}

Whole& Whole::operator=(unsigned __int64 numStr)
{
	parseNum(numStr);
	return *this;
}

Whole& Whole::operator=(std::string numStr)
{
	parseNum(numStr);
	return *this;
}

Whole& Whole::operator=(Whole&& pSrc)
{
	if (this == &pSrc)
		return *this;

	_number = pSrc._number;
	pSrc._number.clear();

	return *this;
}

bool Whole::operator==(const Whole& rhs)
{	
	unsigned __int64 maxPlace = _number.rbegin()->first;
	unsigned __int64 maxPlace2 = rhs._number.rbegin()->first;

	while (true)
	{
		if (_number.find(maxPlace) != _number.end() && rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (maxPlace != maxPlace2)
				return false;
			else
				if (_number.at(maxPlace) != rhs._number.at(maxPlace2))
					return false;			
		}
		else if (_number.find(maxPlace) != _number.end())
		{
			if (_number.at(maxPlace) != 0)
				return false;
		}
		else if (rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (rhs._number.at(maxPlace) != 0)
				return false;
		}
		else
			return true;

		maxPlace--;
		maxPlace2--;		
	}

	return true;
}

bool Whole::operator<(const Whole& rhs)
{
	unsigned __int64 maxPlace = _number.rbegin()->first;
	unsigned __int64 maxPlace2 = rhs._number.rbegin()->first;

	while (true)
	{
		if (_number.find(maxPlace) != _number.end() && rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (maxPlace != maxPlace2)
				return maxPlace < maxPlace2;
			else
				if (_number.at(maxPlace) < rhs._number.at(maxPlace2))
					return true;
		}
		else if (_number.find(maxPlace) != _number.end())
		{
			if (_number.at(maxPlace) >= 0)
				return false;
		}
		else if (rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (rhs._number.at(maxPlace) > 0)
				return true;
		}
		else
			return false;

		maxPlace--;
		maxPlace2--;
	}

	return false;
}

bool Whole::operator>(const Whole& rhs)
{
	unsigned __int64 maxPlace = _number.rbegin()->first;
	unsigned __int64 maxPlace2 = rhs._number.rbegin()->first;

	while (true)
	{
		if (_number.find(maxPlace) != _number.end() && rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (maxPlace != maxPlace2)
				return maxPlace > maxPlace2;
			else
				if (_number.at(maxPlace) > rhs._number.at(maxPlace2))
					return true;
		}
		else if (_number.find(maxPlace) != _number.end())
		{
			if (_number.at(maxPlace) > 0)
				return true;
		}
		else if (rhs._number.find(maxPlace2) != rhs._number.end())
		{
			if (rhs._number.at(maxPlace) >= 0)
				return false;
		}
		else
			return false;

		maxPlace--;
		maxPlace2--;
	}

	return false;
}

bool Whole::operator!=(const Whole& rhs)
{
	return !(this->operator==(rhs));
}

bool Whole::operator<=(const Whole& rhs)
{
	return !(this->operator>(rhs));
}

bool Whole::operator>=(const Whole& rhs)
{
	return !(this->operator<(rhs));
}

Whole Whole::operator+(const Whole& rhs)
{
	unsigned __int64 maxPlace = _number.rbegin()->first;
	unsigned __int64 maxPlace2 = rhs._number.rbegin()->first;
	unsigned __int64 minPlace = _number.begin()->first;
	unsigned __int64 minPlace2 = rhs._number.begin()->first;
	unsigned __int64 minP = (minPlace < minPlace2) ? minPlace : minPlace2;
	unsigned __int64 maxP = (maxPlace < maxPlace2) ? maxPlace : maxPlace2;

	Whole result;

	for (unsigned __int64 i = minP; i <= maxP; i++)
	{
		if (_number.find(i) != _number.end() && rhs._number.find(i) != rhs._number.end())
		{
			unsigned char res = _number.at(i) + rhs._number.at(i);
			unsigned char rem = 0;
			if (res >= 10)
			{
				rem = 1;
				res = res - 10;
				if (result._number.find(i + 1) != result._number.end())
					result._number[i + 1]++;
				else
					result._number[i + 1] = 1;
			}
			if (result._number.find(i) != result._number.end())
				result._number[i] += res;
			else
				result._number[i] = res;
		}
		else if (_number.find(i) != _number.end())
		{
			result._number[i] = _number.at(i);
		}
		else if (rhs._number.find(i) != rhs._number.end())
		{
			result._number[i] = rhs._number.at(i);
		}
	}

	return result;
}

Whole Whole::operator+(unsigned __int64 numStr)
{
	return this->operator+(Whole(numStr));
}

Whole Whole::operator+(std::string numStr)
{
	return this->operator+(Whole(numStr));
}


Whole& Whole::operator+=(const Whole& rhs)

{
	*this = this->operator+(rhs);
	return *this;
}

Whole& Whole::operator+=(unsigned __int64 numStr)

{
	*this = this->operator+(Whole(numStr));
	return *this;
}

Whole& Whole::operator+=(std::string numStr)

{
	*this = this->operator+(Whole(numStr));
	return *this;
}

unsigned char Whole::getPlaceValue(unsigned __int64 place)
{
	if (place >= _number.size())
		return 0;
	return _number[place];
}

Whole Whole::round(unsigned __int64 place)
{
	Whole num(*this);

	if (place > 1)
	{
		if (num._number[place - 1] >= 5)
			 // increase the the rounding digit
			 num._number[place]++;

		// set the test digit to zero
		for (int i = place - 1; i >= 1; i--)
			num._number[i] = 0;
	}

	return num;
}

std::string Whole::toString()
{
	std::string res;
	bool isScientific = _number.begin()->first != 1;
	if (isScientific)
	{
		res.push_back(_number.begin()->second + '0');
		res.push_back('.');
		DigitIterator it = _number.begin(); it++;
		if (it != _number.end())
		{
			res.push_back(it->second + '0');
			it++;
			if (it != _number.end())
				res.push_back(it->second + '0');
		}
		res.push_back('E');
		char buffer[1024];
		sprintf(buffer, "%I64d", _number.rbegin()->first);
		res += buffer;
	}
	else
	{
		for (auto digit : _number)
		{
			res.insert(res.begin(), digit.second + '0');
		}
	}	

	return res;
}

void Whole::parseNum(unsigned __int64 num)
{
	_number.clear();
	unsigned __int64 curPlace = 1;
	while (num > 0)
	{
		unsigned char digit = num - (unsigned __int64(num / 10.0) * 10);
		_number.insert(Digit(curPlace++, digit));
		num = unsigned __int64(num / 10.0);
	}
}

void Whole::parseNum(std::string num)
{
	_number.clear();
	auto matchObj = std::smatch {};
	if (std::regex_match(num, matchObj, std::regex{ "([-+]?)?([0-9]*\\.?[0-9]+)([eE])?([-+]?)([0-9]+)?" }))
	{
		std::string sign = matchObj[1].str();
		std::string number = matchObj[2].str();
		std::string scientific = matchObj[3].str();
		std::string scientificSign = matchObj[4].str();
		std::string power = matchObj[5].str();

		if (sign == "-")
			return;
		if (scientificSign == "-")
			return;
		if (number.empty())
			return;
		if ((number.find('.') != std::string::npos) && scientific.empty())
			return;
		if (!scientific.empty() && power.empty())
			return;

		Whole powerNum;		
		powerNum._number.clear();
		unsigned __int64 curPlace = power.size();
		for (auto ch : power)
			powerNum._number.insert(Digit(curPlace--, ch - '0'));

		unsigned __int64 powerConverted = powerNum;
		curPlace = number.size();
		if ((number.find('.') != std::string::npos))
			curPlace = number.find('.');
		bool foundPeriod = false;
		
		for (auto ch : number)
		{
			if (ch == '0' && _number.size() == 0)
			{
				curPlace--;
				continue;
			}

			if (ch != '.')
			{
				if (powerConverted > 0)
					_number.insert(Digit(powerConverted + curPlace--, ch - '0'));
				else
					_number.insert(Digit(curPlace--, ch - '0'));
			}
		}
	}
}

unsigned __int64 Whole::getValue() const
{
	unsigned __int64 val = 0;
	for (auto digit : _number)
	{
		unsigned __int64 pow = 1;
		for (int i = 1; i < digit.first; i++)
			pow *= 10;
		val += digit.second * pow;
	}		

	return val;
}